package com.hp.usage.gr.initialization;

import com.hp.siu.collector.rules.RuleChain;
import com.hp.siu.collector.rules.RuleException;
import com.hp.siu.logging.Level;
import com.hp.siu.logging.Logger;
import com.hp.siu.serverutils.SIUServer;
import com.hp.siu.utils.*;
import com.hp.snap.deployer.model.ActiveStandby;
import com.hp.snap.deployer.model.Host;
import com.hp.snap.deployer.model.PNFC;
import com.hp.snap.deployer.model.PNFR;
import com.hp.usage.gr.db.DbBasedRule;
import com.hp.usage.jmx.transport.ConnectorFactory;
import com.hp.usage.jmx.transport.JmxConnectorFactory;
import com.hp.usage.nme.*;
import com.hp.usage.nme.NMEType;
import com.hp.usage.remoting.registry.http.HttpRegistryClient;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.representer.Representer;

import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by huxiaoji on 1/18/2016.
 */
public class GrAccessPointQuery extends DbBasedRule{
    public static final Map<String, String[]> SUB_SYSTEM_MAPPING = new HashMap<>();
    public static final Map<String, String[]> CONNECTOR_MAPPING = new HashMap<>();
    static {
        SUB_SYSTEM_MAPPING.put("OCS", new String[]{"ocsServer"});
        SUB_SYSTEM_MAPPING.put("MGR", new String[]{"managementServer"});
        SUB_SYSTEM_MAPPING.put("SPR Proxy", new String[]{"sprServer"});
        SUB_SYSTEM_MAPPING.put("MNF", new String[]{"mnfServer"});
        SUB_SYSTEM_MAPPING.put("LB", new String[]{"lbServer"});
        SUB_SYSTEM_MAPPING.put("DB", new String[]{"snapDB","sprDB", "sessionDB", "lbDB", "cdrDB"});
        SUB_SYSTEM_MAPPING.put("SNAPStudio", new String[]{"studioServer"});

        CONNECTOR_MAPPING.put("Ro", new String[]{"DiameterConnector_Ro", "roInput"});
        CONNECTOR_MAPPING.put("Sy", new String[]{"DiameterConnector_Sy", "syInput"});
        CONNECTOR_MAPPING.put("Rof",new String[]{"DiameterConnector_Rof", "rofInput"});
    }

    //    public static final String PROP_
    private static Logger log_ = Logger.getLogger("com.hp.usage.gr.initialization.GrAccessPointQuery", "com.hp.usage.gr.message_catalog");
    private RuleChain errorRule_ = null;
    private ConfigManager configManager;

    @Override
    public void applyRule(NormalizedMeteredEvent nme) throws RuleException {
        if (log_.isLoggable(Level.DEBUG4)){
            log_.logDebug4("applyRule(" + nme + ")");
        }
        Attribute attr = nme.getAttribute("GR_CONNECTOR");
        if (attr == null){
            throw new RuleException("GR_CONNECTOR is mandatory for this Rule.");
        }
        String connector = attr.toString();

        attr = nme.getAttribute("GR_SUB_SYSTEM");
        String subSystem = null;
        if (attr != null){
            subSystem = attr.toString();
        }

        attr = nme.getAttribute("GR_CURRENT_SITE_ID");
        if (attr == null){
            throw new RuleException("SMAN system may not initialized.");
        }
        String siteId = attr.toString();

        if (log_.isLoggable(Level.DEBUG4)){
            log_.logDebug4("query info for subsystem: " + subSystem + ", connector: " + connector);
        }
        try {
            Connection conn = getConnection();
            try{
                ResultSet rs = conn.createStatement().executeQuery("select CONTENT from SNAP_DEPLOYER where ID = 0");
                if (rs.next()){
                    String yaml = rs.getString(1);

                    query(nme, yaml, subSystem, connector, siteId);
                } else {
                    log_.warning("SNAP Deployer info haven't initialized, please finish SNAP software install first.");
                    handleError(new Exception("SNAP Deployer info haven't initialized"), nme);
                }
            } finally {
                releaseConnection(conn);
            }
        } catch (SQLException e){
            log_.logError(e.getMessage(), e);
            handleError(e, nme);
        }
    }

    public JMXConnector getCentralJMXConnector() {
        try {
            HttpRegistryClient client = new HttpRegistryClient(SIUInfo.getTopologyServiceURI());
            JmxConnectorFactory factory = ConnectorFactory.createFactory(client, 10);
            JMXConnector connector = factory.createConnector(SIUServer.getLoginContext());
            return connector;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public String getCentralJmxUrl() {
        try {
            String url = "iiop:/collectors/ManagementServer/ManagementServiceInterface";
            if(SIUInfo.getInstance().isSecure()) {
                url = "iiop:/collectors/ManagementServer/SecureManagementServiceInterface";
            }

            String ior = ConnectorFactory.getServiceIOR(url, SIUServer.getLoginContext());
            if(log_.isLoggable(Level.DEBUG)) {
                log_.logDebug("central ior--->" + ior);
            }

            return "service:jmx:iiop:///ior/" + ior;
        } catch (Exception e) {
            log_.logException(Level.WARNING, "Failed to read Central JMX URL!", e);
            return "";
        }
    }

    private void queryJMX(){
        try {
            log_.info("central: " + getCentralJmxUrl());
            JMXConnector c = getCentralJMXConnector();
            log_.info("domain: " + c.getMBeanServerConnection().getDefaultDomain());

            String id = "*/ATOM:type=Loadbalancer,name=AudoDiscoverSupportor";
            ObjectName obj = ObjectName.getInstance(id);
            MBeanServerConnection mbsc = c.getMBeanServerConnection();
            Set<ObjectName> rs = mbsc.queryNames(obj, null);
            log_.info("rs: " + rs);

            c.close();
        } catch (Exception e){
            log_.info(e.getMessage());
        }
    }

    private void handleError(Exception e, NormalizedMeteredEvent nme) throws RuleException{
        if (errorRule_ != null){
            if (this.next_ != null) this.next_ = null;
            errorRule_.applyRule(nme, nme1);
        } else {
            throw new RuleException(e.getMessage(), e);
        }
    }

    private void query(NormalizedMeteredEvent nme, String yaml, String subSystem, String connector, String siteId) throws RuleException{
        DumperOptions options = new DumperOptions();
        options.setPrettyFlow(true);
        options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
        Representer representer = new Representer();

        Yaml yamlHelper = new Yaml(representer, options);

        PNFR info = (PNFR) yamlHelper.load(yaml);

        NME snme = getSNME(nme);

        PNFC mgr = info.getPnfcs().get(SUB_SYSTEM_MAPPING.get("MGR")[0]);
        Map<String, String> rtcconf = mgr.getConfigurations().get(((ActiveStandby) mgr.getHighAvailable()).getActive().getId()).get("rtc");
        Map<String, Config> cfgCache = new HashMap<>();

        if (connector.equals("Gy")) connector = "Ro";
        if (connector.equals("SPR")){
            if (subSystem == null){
                buildSPRForSPRP(snme, info, connector, siteId, rtcconf);
            } else if (subSystem.equals("SPRP")) {
                buildSPRForSPRP(snme, info, connector, siteId, rtcconf);
            } else if (subSystem.equals("LB")){
                buildSPRForLB(snme, info, connector, siteId, rtcconf);
            }
        } else if (connector.equals("SSH")) {
            if (isEmpty(subSystem)) {
                for (String key : SUB_SYSTEM_MAPPING.keySet()) {
                    buildSSH(snme, info, key, connector, siteId, rtcconf);
                }
            } else {
                buildSSH(snme, info, subSystem, connector, siteId, rtcconf);
            }
        } else if (connector.equals("VoltDB")){
            if (isEmpty(subSystem)) {
                for (String key : SUB_SYSTEM_MAPPING.keySet()) {
                    buildVoltDB(snme, info, key, connector, siteId, rtcconf);
                }
            } else {
                buildVoltDB(snme, info, subSystem, connector, siteId, rtcconf);
            }
        } else if (connector.equals("MysqlDB")){
            if (isEmpty(subSystem)) {
                for (String key : SUB_SYSTEM_MAPPING.keySet()) {
                    buildMysqlDB(snme, info, key, connector, siteId, rtcconf);
                }
            } else {
                buildMysqlDB(snme, info, subSystem, connector, siteId, rtcconf);
            }
        } else {
            if (buildDiameterInfo(snme, cfgCache, info, subSystem, connector, siteId) == 0){
                setResultCode(snme, "4006");
            }
        }

        setResultCode(snme, "2001");
    }

    private int buildDiameterInfo(NME snme, Map<String, Config> cfgCache, PNFR info, String subSystem, String connector, String siteId) {
        String[] conn = CONNECTOR_MAPPING.get(connector);
        if (conn == null) return 0;
        int c = 0;
        for (String sys : SUB_SYSTEM_MAPPING.get(subSystem)){
            PNFC pnfc = info.getPnfcs().get(sys);
            if (pnfc != null) {
                NMEMutableArray array = buildResponse(snme, pnfc.getHosts().size());

                for (Host h : pnfc.getHosts()){
                    String hostId = getHostId(pnfc.getConfigurations().get(h.getId()));
                    String serverName = getRtcServerName(pnfc.getConfigurations().get(h.getId()));
                    Config root = getRootCfg(cfgCache, hostId, serverName);
                    if (root == null) continue;

                    String sub = getRtcSubSystem(root);

                    if (sub != null && sub.equals(subSystem)){
                        int r = buildDiameterForRTP(array, root, subSystem, connector, siteId, h.getHostip(), serverName, conn);
                        if (r == 0)
                            buildDiameterForIUM(array, root, subSystem, connector, siteId, h.getHostip(), serverName, conn);
                        c += r;
                    }
                }
            }
        }
        return c;
    }

    private Config getNode(Config root, String path){
        String[] ps = path.split("/");
        Config n = root;
        for (String p : ps){
            try {
                if (n == null) return null;

                n = n.getConfigEntry(p);
            } catch (ShallowEntryException e) {
                log_.logError(e.getMessage());
                return null;
            }
        }
        return n;
    }

    private Config getRootCfg(Map<String, Config> cache, String hostId, String serverName){
        String key = String.format("/deployment/%s/%s", hostId, serverName);
        Config root = cache.get(key);
        if (root == null){
            try {
                root = configManager.getConfigTree(key, true, true);

                cache.put(key, root);
            } catch (ClientException e) {
                log_.logError(e.getMessage());
            }
        }
        return root;
    }

    private static final Pattern PORT_PATTERN = Pattern.compile(":(\\d+)");
    private int buildDiameterForIUM(NMEMutableArray array, Config root, String subSystem, String connector, String siteId, String hostIp, String serverName, String[] conn) {
        for (String co : conn) {
            Config cfg = getNode(root, String.format("Connectors/%s", co));

            if (cfg != null) {
                String serverHost = cfg.getAttribute("ServerHost");
                String serverRealm = cfg.getAttribute("ServerRealm");

                Config t;
                try {
                    t = cfg.getConfigEntry("Transport");
                } catch (ShallowEntryException e) {
                    log_.logError(e.getMessage());
                    continue;
                }
                if (t != null){
                    String port = t.getAttribute("PortName");

                    String connInfo = String.format("%s;%s;%s;%s", serverHost, serverRealm, hostIp, port);

                    buildRecord(array, siteId, subSystem, connector, serverName, connInfo);
                    return 1;
                }
            }
        }
        return 0;
    }

    private int buildDiameterForRTP(NMEMutableArray array, Config root, String subSystem, String connector, String siteId, String hostIp, String serverName, String[] conn) {
        for (String co : conn) {
            Config cfg = getNode(root, String.format("RTP/Configuration/%s", co));

            if (cfg != null) {
                String serverHost = cfg.getAttribute("nodeInfo.serverHost");
                String serverRealm = cfg.getAttribute("nodeInfo.serverRealm");
                String bindAddr = cfg.getAttribute("bindAddress");

                Matcher m = PORT_PATTERN.matcher(bindAddr);
                if (m.find()) {
                    String port = m.group(1);

                    String connInfo = String.format("%s;%s;%s;%s", serverHost, serverRealm, hostIp, port);

                    buildRecord(array, siteId, subSystem, connector, serverName, connInfo);

                    return 1;
                }
            }
        }
        return 0;
    }

    private String getHostId(Map<String, Map<String, String>> map){
        if (map == null) return "";
        Map<String, String> vmap = map.get("eIUM");
        if (vmap == null) return "";
        String value = vmap.get("host_id");
        return value == null?"":value;
    }

    private String getRtcSubSystem(Config root){
        String link = root.getAttribute("Link");
        if (link == null) return null;

        return link.substring(link.lastIndexOf("/")+1);
    }

    private void buildSPRForLB(NME snme, PNFR info, String connector, String siteId, Map<String, String> rtcconf) {

    }

    private void buildDiameterForLB(NME snme, PNFR info, String connector, String siteId, Map<String, String> rtcconf) {
        PNFC pnfc = info.getPnfcs().get(SUB_SYSTEM_MAPPING.get("LB")[0]);
        if (pnfc != null){
            NMEMutableArray array = buildResponse(snme, pnfc.getHosts().size());

            String port = rtcconf.get("lb_" + connector.toLowerCase() + "_port");
            String dhost = rtcconf.get("lb_" + connector.toLowerCase() + "_host");
            String drealm = rtcconf.get("lb_" + connector.toLowerCase() + "_realm");
            for (Host h : pnfc.getHosts()){
                String connInfo = String.format("%s;%s;%s;%s", dhost, drealm, h.getHostip(), port);

                String serverName = getRtcServerName(pnfc.getConfigurations().get(h.getId()));
                buildRecord(array, siteId, "LB", connector, serverName, connInfo);
            }
        }
    }

    private void buildMysqlDB(NME snme, PNFR info, String subSystem, String connector, String siteId, Map<String, String> rtcconf) {
        if (!subSystem.equals("DB")) return;
        String[] dbnames = SUB_SYSTEM_MAPPING.get("DB");
        NMEMutableArray array = buildResponse(snme, dbnames.length);
        for (String db : dbnames){
            PNFC pnfc = info.getPnfcs().get(db);
            if (pnfc != null){
                for (Map.Entry<String, Map<String, Map<String, String>>> entry : pnfc.getConfigurations().entrySet()){
                    String serverName = entry.getKey();

                    Map<String, String> dbconf = entry.getValue().get(db);
                    if (dbconf != null){

                        String connInfo = String.format("dbHost=%s,dbPort=%s,dbClass=%s,dbName=%s,user=%s,password=%s",
                                dbconf.get("db_host"), dbconf.get("db_port"), dbconf.get("db_driver"),
                                dbconf.get("db_serv"), dbconf.get("db_user"), dbconf.get("db_pass"));

                        buildRecord(array, siteId, subSystem, connector, serverName, connInfo);
                    }
                }
            }
        }
    }

    private void buildVoltDB(NME snme, PNFR info, String subSystem, String connector, String siteId, Map<String, String> rtcconf) {
        if (!subSystem.equals("DB")) return;
        String[] dbnames = SUB_SYSTEM_MAPPING.get("DB");
        PNFC mgr = info.getPnfcs().get(SUB_SYSTEM_MAPPING.get("MGR")[0]);
        Map<String, Map<String, String>> config = mgr.getConfigurations().get(((ActiveStandby) mgr.getHighAvailable()).getActive().getId());

        NMEMutableArray array = buildResponse(snme, dbnames.length);
        for (String db : dbnames){
            Map<String, String> dbconf = config.get(db);
            if (dbconf != null){
                String serverName = dbconf.get("database_name");
                String connInfo = String.format("vemHost=%s,vemPort=%s,user=%s,password=%s,clientPort=%s", dbconf.get("listen_address"),
                        dbconf.get("voltdb_vem_port"), dbconf.get("db_username"), dbconf.get("db_pass"), dbconf.get("voltdb_client_port"));
                buildRecord(array, siteId, subSystem, connector, serverName, connInfo);
            }
        }
    }

    private void buildSSH(NME snme, PNFR info, String subSystem, String connector, String siteId, Map<String, String> rtcconf) {
        String[] system = SUB_SYSTEM_MAPPING.get(subSystem);
        if (system == null) return;
        for (String sys : system) {
            PNFC pnfc = info.getPnfcs().get(sys);
            if (pnfc != null) {
                NMEMutableArray array = buildResponse(snme, pnfc.getHosts().size());
                for (Host h : pnfc.getHosts()) {

                    Map<String, Map<String, String>> map = pnfc.getConfigurations().get(h.getId());
                    Map<String, String> vmap = map == null ? null : map.get("eIUM");

                    String eIUMInstance = vmap == null ? "" : vmap.get("ium_instance_name");
                    String eIUMHostId = vmap == null ? "" : vmap.get("host_id");

                    vmap = map == null ? null : map.get("rtcInstance");
                    String serverName = vmap == null ? "" : vmap.get("rtc_ss_name");

                    if (serverName == null || serverName.equals(""))
                        serverName = sys;

                    vmap = map == null ? null : map.get("rtcInstall");
                    String rtcHome = vmap == null ? "" : vmap.get("rtc_install_dir");

                    String connInfo = String.format("hostIp=%s,hostName=%s,login=%s,eIUMHostId=%s,eIUMInstance=%s,rtcHome=%s,sshKey=%s",
                            h.getHostip(), h.getHostname(), h.getLogin(), eIUMHostId, eIUMInstance, rtcHome, h.getKey());

                    buildRecord(array, siteId, subSystem, connector, serverName, connInfo);
                }
            }
        }
    }

    private void buildSPRForSPRP(NME snme, PNFR info, String connector, String siteId, Map<String, String> rtcconf) {
        PNFC spr = info.getPnfcs().get(SUB_SYSTEM_MAPPING.get("SPRP")[0]);
        if (spr != null) {
            String port = rtcconf.get("spr_http_port");

            NMEMutableArray array = buildResponse(snme, spr.getHosts().size());
            for (Host h : spr.getHosts()) {
                String connInfo = String.format("%s:%s", h.getHostip(), port);

                String serverName = getRtcServerName(spr.getConfigurations().get(h.getId()));
                buildRecord(array, siteId, "SPRP", connector, serverName, connInfo);
            }
        }
    }

    private void buildDiameterForOCS(NME snme, PNFR info, String connector, String siteId, Map<String, String> rtcconf){
        PNFC pnfc = info.getPnfcs().get(SUB_SYSTEM_MAPPING.get("OCS")[0]);
        if (pnfc != null){
            NMEMutableArray array = buildResponse(snme, pnfc.getHosts().size());

            String port = rtcconf.get(connector + "_port");
            String dhost = rtcconf.get(connector + "_host");
            String drealm = rtcconf.get(connector + "_realm");
            for (Host h : pnfc.getHosts()){
                String connInfo = String.format("%s;%s;%s;%s", dhost, drealm, h.getHostip(), port);

                String serverName = getRtcServerName(pnfc.getConfigurations().get(h.getId()));
                buildRecord(array, siteId, "OCS", connector, serverName, connInfo);
            }
        }
    }

    private String getRtcServerName(Map<String, Map<String, String>> map) {
        if (map == null) return "";
        Map<String, String> vmap = map.get("rtcInstance");
        if (vmap == null) return "";
        String value = vmap.get("rtc_ss_name");
        return value == null?"":value;
    }

    private void buildRecord(NMEMutableArray array, String siteId, String subSystem, String connector, String hostname, String connInfo) {
        NME snmeRecord = createEmptyNME("gr:recordApdsRecordType");
        setInt(snmeRecord, "siteId", siteId);
        setString(snmeRecord, "subSystem", subSystem);
        setString(snmeRecord, "connectorName", connector);
        setString(snmeRecord, "serverName", hostname);
        setString(snmeRecord, "connectorInfo", connInfo);

        array.add(snmeRecord);
    }

    private NMEMutableArray buildResponse(NME snme, int num){
        AttributeRef ref = snme.getAttributeRef("response");
        NME snmeApdsResponse;
        if (snme.isAttributeSet(ref)){
            snmeApdsResponse = snme.getStruct(ref);
        } else {
            snmeApdsResponse = createEmptyNME("gr:ResponseType");
            snme.setStruct(ref, snmeApdsResponse);
        }
        ref = snmeApdsResponse.getAttributeRef("apResult");
        NME snmeResult;
        if (snmeApdsResponse.isAttributeSet(ref)){
            snmeResult = snmeApdsResponse.getStruct(ref);
        } else {
            snmeResult = createEmptyNME("gr:ApdsResultType");
            snmeApdsResponse.setStruct(ref, snmeResult);
        }

        ref = snmeResult.getAttributeRef("record");
        NMEMutableArray array;
        if (snmeResult.isAttributeSet(ref)){
            array = (NMEMutableArray) snmeResult.getArray(ref);
            array.ensureCapacity(array.length()+num);
        } else {
            array = createNewArray(num);
            snmeResult.setArray(snmeResult.getAttributeRef("record"), array);
        }

        return array;
    }

    @Override
    public void configure(Config config) throws ConfigurationException {
        super.configure(config);
        Config errorRule = config.getConfigEntry("ErrorRule");
        if (errorRule != null){
            this.errorRule_ = (RuleChain)config.loadClass("ErrorRule");
        }

        configManager = new ConfigManager(SIUServer.getLoginContext());
    }
}
